package gui.view;

import gui.controller.IllegalUserInputException;
import gui.controller.PropertyChangeConstants;
import gui.controller.SolveSupport;
import helperClasses.GuiHelper;
import helperClasses.Help;

import java.awt.Component;
import java.awt.GridLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import javax.swing.JOptionPane;
import javax.swing.JPanel;

import solverLogic.Solver;
import dataTypes.Sudoku;
import dataTypes.SudokuImpl;

/**
 * 
 * @author Felix Schliephacke
 * 
 */
class PlayingField extends JPanel implements PropertyChangeListener {

	private static final long serialVersionUID = 1123758186253167881L;
	private Sudoku<Integer> restore;
	private Iterator<Sudoku<Integer>> solutionIterator;
	private List<Sudoku<Integer>> solutions;
	private SolveSupport<Integer> solveSupport;

	public PlayingField() {
		this(3);
	}

	public PlayingField(final int size) {
		this(SudokuImpl.getInstance(size));
	}

	public PlayingField(final Sudoku<Integer> sudoku) {
		this.initField(sudoku);
	}

	private void changeSize(final int size) {
		final int tempSize = (int) Math.sqrt(size);
		this.initField(SudokuImpl.getInstance(tempSize));
	}

	private void clear() {
		this.solutionIterator = null;
		this.solveSupport = null;
		this.solutions = null;
		for (final Component temp : this.getComponents()) {
			if (temp instanceof PlayingSquare) {
				((PlayingSquare) temp).clear();
			}
		}
	}

	private void initField(final Sudoku<Integer> sudoku) {
		this.setVisible(false);
		final Sudoku<Integer> tempSudoku = sudoku.clone();
		int tempSize = tempSudoku.getSize();
		int columnWidth = 0;
		while (tempSize > 0) {
			columnWidth++;
			tempSize /= 10;
		}
		this.removeAll();
		this.setLayout(new GridLayout((int) Math.sqrt(tempSudoku.getSize()),
				(int) Math.sqrt(tempSudoku.getSize())));
		for (int i = 0; i < tempSudoku.getSize(); i++) {
			this.add(new PlayingSquare(tempSudoku.getSquare(i), columnWidth));
		}
		this.setVisible(true);
	}

	private void nextSolution() {
		if (this.solutionIterator != null) {
			try {
				this.initField(this.solutionIterator.next());
			} catch (final NoSuchElementException e) {
				this.solutionIterator = this.solutions.iterator();
				// this.solutionIterator = DatabaseConnection.getInstance()
				// .iterator();
				JOptionPane.showMessageDialog(this,
						"No more solutions available. "
								+ "Switching to first one again.",
						"Last Solution", JOptionPane.INFORMATION_MESSAGE);
			}
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void propertyChange(final PropertyChangeEvent arg0)
			throws NumberFormatException {
		if (arg0.getPropertyName().equals(
				PropertyChangeConstants.CLEAR.toString())) {
			this.clear();
		} else if (arg0.getPropertyName().equals(
				PropertyChangeConstants.CHANGESIZE.toString())) {
			this.changeSize(GuiHelper.trimSizeChoice(arg0.getNewValue()
					.toString()));
		} else if (arg0.getPropertyName().equals(
				PropertyChangeConstants.SOLVE.toString())) {
			this.solve();
		} else if (arg0.getPropertyName().equals(
				PropertyChangeConstants.SOLVED.toString())) {
			if (arg0.getNewValue() instanceof List) {
				this.solved((List<Sudoku<Integer>>) arg0.getNewValue());
			}
		} else if (arg0.getPropertyName().equals(
				PropertyChangeConstants.NEXTSOLUTION.toString())) {
			this.nextSolution();
		} else if (arg0.getPropertyName().equals(
				PropertyChangeConstants.RESTORE.toString())) {
			this.restore();
		} else if (arg0.getPropertyName().equals(
				PropertyChangeConstants.MAXTHREADS.toString())) {
			final int threads = Integer.parseInt(arg0.getNewValue().toString());
			if (threads <= 0) {
				throw new NumberFormatException(
						"Number of threads must be at least 1.");
			}
			this.setMaxThreads(threads);
		} else if (arg0.getPropertyName().equals(
				PropertyChangeConstants.MAXSOLUTIONS.toString())) {
			final int solutions = Integer.parseInt(arg0.getNewValue()
					.toString());
			if (solutions <= 0) {
				throw new NumberFormatException(
						"Number of maximum solutions must be greater than 0.");
			}
			Solver.setMaxSolutions(solutions);
		}
	}

	public Sudoku<Integer> readSudoku() throws IllegalUserInputException {
		final Integer[][] sudoku = new Integer[this.getComponentCount()][this
				.getComponentCount()];
		for (int i = 0; i < this.getComponentCount(); i++) {
			final Integer[] square = ((PlayingSquare) this.getComponent(i))
					.getSquare();
			final int[] rows = Help.calculateRowIndices(i, (int) Math.sqrt(this
					.getComponentCount()));
			final int[] columns = Help.calculateColumnIndices(i, (int) Math
					.sqrt(this.getComponentCount()));
			int squareCount = 0;
			for (final int row : rows) {
				for (final int column : columns) {
					sudoku[row][column] = square[squareCount++];
				}
			}
		}

		return SudokuImpl.getInstance(sudoku);
	}

	private void restore() {
		if (this.restore != null) {
			this.initField(this.restore);
		}
	}

	private void setMaxThreads(final int threads) {
		Solver.setMaxThreads(threads);
	}

	private void solve() {
		try {
			this.restore = null;
			this.solveSupport = null;
			this.solutionIterator = null;
			this.solutions = null;
			this.restore = this.readSudoku();
			this.solveSupport = new SolveSupport<Integer>(this.restore);
			this.solveSupport.addPropertyChangeListener(this);
		} catch (final IllegalUserInputException e) {
			JOptionPane.showMessageDialog(this, e.getLocalizedMessage(),
					"Illegal Input", JOptionPane.ERROR_MESSAGE);
		}
	}

	private void solved(final List<Sudoku<Integer>> solutions) {
		// this.solutionIterator = DatabaseConnection.getInstance().iterator();
		this.solutionIterator = null;
		this.solutions = null;
		if (solutions.size() > 0) {
			// if (this.solutionIterator.hasNext()) {
			this.solveSupport.removePropertyChangeListener(this);
			this.solveSupport = null;
			this.solutions = solutions;
			this.solutionIterator = this.solutions.iterator();
			this.initField(this.solutionIterator.next());
		} else {
			JOptionPane.showMessageDialog(this, "No solution found.");
			this.initField(this.restore);
		}
	}
}
